﻿#region License
// (c) Intergen.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using TextGlow.Control.Utilities;

namespace TextGlow.Control.Model
{
  public class RunProperties : ContentProperties
  {
    private Style _definingStyle;

    private bool? _bold;
    private Border _borderText;
    private bool? _caps;
    private DocumentColor? _color;
    private bool? _doubleStrikethrough;
    private string _emphasis;
    private string _highlight;
    private bool? _italics;
    private int? _fontKerning;
    private Fonts _fonts;
    private string _styleId;
    private bool? _smallCaps;
    private int? _characterSpacing;
    private bool? _strikeThrough;
    private Unit? _fontSize;
    private UnderlinePattern? _underline;
    private DocumentColor? _underlineColor;
    private string _verticalAlignment;

    public Fonts Fonts
    {
      get { return _fonts; }
      set { _fonts = value; }
    }

    public string VerticalAlignment
    {
      get { return GetValue(p => p._verticalAlignment); }
      set { _verticalAlignment = value; }
    }

    public DocumentColor? UnderlineColor
    {
      get { return GetValue(p => p._underlineColor); }
      set { _underlineColor = value; }
    }

    public UnderlinePattern? Underline
    {
      get { return GetValue(p => p._underline); }
      set { _underline = value; }
    }

    public Unit? FontSize
    {
      get { return GetValue(p => p._fontSize); }
      set { _fontSize = value; }
    }

    public bool? StrikeThrough
    {
      get { return GetValue(p => p._strikeThrough); }
      set { _strikeThrough = value; }
    }

    public int? CharacterSpacing
    {
      get { return GetValue(p => p._characterSpacing); }
      set { _characterSpacing = value; }
    }

    public bool? SmallCaps
    {
      get { return GetValue(p => p._smallCaps); }
      set { _smallCaps = value; }
    }

    public Style Style
    {
      get { return DocumentContent.GetStyle(Content, _styleId); }
    }

    public string StyleId
    {
      get { return GetValue(p => p._styleId); }
      set { _styleId = value; }
    }

    public int? FontKerning
    {
      get { return GetValue(p => p._fontKerning); }
      set { _fontKerning = value; }
    }

    public bool? Italics
    {
      get { return GetValue(p => p._italics); }
      set { _italics = value; }
    }

    public string Highlight
    {
      get { return GetValue(p => p._highlight); }
      set { _highlight = value; }
    }

    public string Emphasis
    {
      get { return GetValue(p => p._emphasis); }
      set { _emphasis = value; }
    }

    public bool? DoubleStrikethrough
    {
      get { return GetValue(p => p._doubleStrikethrough); }
      set { _doubleStrikethrough = value; }
    }

    public DocumentColor? Color
    {
      get { return GetValue(p => p._color); }
      set { _color = value; }
    }

    public bool? Caps
    {
      get { return GetValue(p => p._caps); }
      set { _caps = value; }
    }

    public Border BorderText
    {
      get { return _borderText; }
      set { _borderText = value; }
    }

    public bool? Bold
    {
      get { return GetValue(p => p._bold); }
      set { _bold = value; }
    }

    public RunProperties(Style definingStyle, XElement element)
      : this(definingStyle.ParentContent, element)
    {
      if (definingStyle == null)
        throw new ArgumentNullException("definingStyle");

      _definingStyle = definingStyle;
    }

    public RunProperties(Content parentContent, XElement element)
      : base(parentContent)
    {
      if (element == null)
      {
        _fonts = new Fonts(this, null);
        return;
      }

      XNamespace ns = element.Name.Namespace;

      _bold = XmlUtils.GetBoolean(element.Element(ns + "b"));
      _borderText = Border.Create(this,
        element.Element(ns + "bdr"),
        g => GetValue<object>(p => g(p._borderText)));
      _caps = XmlUtils.GetBoolean(element.Element(ns + "caps"));
      _color = DocumentColor.Parse(XmlUtils.GetValue(element.Element(ns + "color")));
      _doubleStrikethrough = XmlUtils.GetBoolean(element.Element(ns + "dstrike"));
      _emphasis = XmlUtils.GetValue(element.Element(ns + "em"));
      _highlight = XmlUtils.GetValue(element.Element(ns + "highlight"));
      _italics = XmlUtils.GetBoolean(element.Element(ns + "i"));
      _fontKerning = XmlUtils.GetValue<int>(element.Element(ns + "kern"));
      _fonts = new Fonts(this, element.Element(ns + "rFonts"));
      _styleId = XmlUtils.GetValue(element.Element(ns + "rStyle"));
      _smallCaps = XmlUtils.GetBoolean(element.Element(ns + "smallCaps"));
      _characterSpacing = XmlUtils.GetValue<int>(element.Element(ns + "spacing"));
      _strikeThrough = XmlUtils.GetBoolean(element.Element(ns + "strike"));
      _fontSize = Unit.HalfOfAPoint(XmlUtils.GetValue<int>(element.Element(ns + "sz")));
      _underline = XmlUtils.GetValue<UnderlinePattern>(element.Element(ns + "u"));
      _underlineColor = DocumentColor.Parse(XmlUtils.GetValue(element.Element(ns + "u"), "color"));
      _verticalAlignment = XmlUtils.GetValue(element.Element(ns + "vertAlign"));
    }

    public T GetValue<T>(Func<RunProperties, T> valueGetter)
    {
      if (valueGetter == null)
        throw new ArgumentNullException("valueGetter");

      Content parent = Content;

      List<Func<T>> attempts = new List<Func<T>>();

      // current property
      attempts.Add(() => valueGetter(this));

      if (parent is Run)
      {
        Run run = (Run)parent;
        Paragraph paragraph = run.Parent;

        AddParagraphAttempts(paragraph, valueGetter, attempts);
      }
      else if (parent is Paragraph)
      {
        AddParagraphAttempts((Paragraph)parent, valueGetter, attempts);
      }
      else if (_definingStyle != null && _definingStyle.BasedOnStyle != null)
      {
        attempts.Add(() => valueGetter(_definingStyle.BasedOnStyle.RunProperties));
      }

      return MiscellaneousUtils.EvaluateUntilValue(attempts);
    }

    private void AddStyleAttempts<T>(Style style, Func<RunProperties, T> valueGetter, List<Func<T>> attempts)
    {
      if (style == null)
        return;

      attempts.Add(() => valueGetter(style.RunProperties));
      if (style.BasedOnStyle != null)
        attempts.Add(() => valueGetter(style.BasedOnStyle.RunProperties));
    }

    private void AddParagraphAttempts<T>(Paragraph paragraph, Func<RunProperties, T> valueGetter, List<Func<T>> attempts)
    {
      AddStyleAttempts(Style, valueGetter, attempts);

      AddStyleAttempts(paragraph.Properties.Style, valueGetter, attempts);

      Table parentTable = Content.GetParentTable(paragraph);

      if (parentTable != null && parentTable.Properties.Style != null)
      {
        Style parentTableStyle = parentTable.Properties.Style;
        TableCell parentCell = Content.GetParentCell(paragraph);
        TableRow parentRow = Content.GetParentRow(paragraph);

        // paragraph table conditional style
        if (paragraph.Properties.ConditionalFormatting != null)
          attempts.Add(() => ConditionalTableStyle.GetConditionalTableStyleValue(valueGetter,
            s => s.RunProperties,
            paragraph.Properties.ConditionalFormatting.Value,
            parentTableStyle.ConditionalTableStyles));

        // cell table conditional style
        if (parentCell.Properties.ConditionalFormatting != null)
          attempts.Add(() => ConditionalTableStyle.GetConditionalTableStyleValue(valueGetter,
            s => s.RunProperties,
            parentCell.Properties.ConditionalFormatting.Value,
            parentTableStyle.ConditionalTableStyles));

        // row table conditional style
        if (parentRow.Properties.ConditionalFormatting != null)
          attempts.Add(() => ConditionalTableStyle.GetConditionalTableStyleValue(valueGetter,
            s => s.RunProperties,
            parentRow.Properties.ConditionalFormatting.Value,
            parentTableStyle.ConditionalTableStyles));

        // table style
        AddStyleAttempts(parentTableStyle, valueGetter, attempts);
      }

      // document defaults
      attempts.Add(() => valueGetter(paragraph.MainDocument.DefaultRunProperties));
    }
  }
}